// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/views/tabs/tab_scrubber_chromeos.h"

#include <memory>
#include <utility>

#include "ash/constants/ash_switches.h"
#include "ash/display/event_transformation_handler.h"
#include "ash/shell.h"
#include "base/command_line.h"
#include "base/run_loop.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "content/public/browser/notification_service.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "ui/aura/window.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.h"

namespace {

constexpr int kScrubbingGestureFingerCount = 3;

// Waits until the immersive mode reveal ends, and therefore the top view of
// the browser is no longer visible.
class ImmersiveRevealEndedWaiter : public ImmersiveModeController::Observer {
 public:
  explicit ImmersiveRevealEndedWaiter(
      ImmersiveModeController* immersive_controller)
      : immersive_controller_(immersive_controller) {
    immersive_controller_->AddObserver(this);
  }

  ImmersiveRevealEndedWaiter(const ImmersiveRevealEndedWaiter&) = delete;
  ImmersiveRevealEndedWaiter& operator=(const ImmersiveRevealEndedWaiter&) =
      delete;

  ~ImmersiveRevealEndedWaiter() override {
    if (immersive_controller_)
      immersive_controller_->RemoveObserver(this);
  }

  void Wait() {
    if (!immersive_controller_ || !immersive_controller_->IsRevealed())
      return;

    base::RunLoop run_loop;
    quit_closure_ = run_loop.QuitClosure();
    run_loop.Run();
  }

 private:
  void MaybeQuitRunLoop() {
    if (!quit_closure_.is_null())
      std::move(quit_closure_).Run();
  }

  // ImmersiveModeController::Observer:
  void OnImmersiveRevealEnded() override { MaybeQuitRunLoop(); }

  void OnImmersiveModeControllerDestroyed() override {
    MaybeQuitRunLoop();
    immersive_controller_->RemoveObserver(this);
    immersive_controller_ = nullptr;
  }

  ImmersiveModeController* immersive_controller_;
  base::OnceClosure quit_closure_;
};

}  // namespace

class TabScrubberChromeOSTest : public InProcessBrowserTest,
                                public TabStripModelObserver {
 public:
  TabScrubberChromeOSTest() = default;

  TabScrubberChromeOSTest(const TabScrubberChromeOSTest&) = delete;
  TabScrubberChromeOSTest& operator=(const TabScrubberChromeOSTest&) = delete;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(ash::switches::kNaturalScrollDefault);
  }

  void SetUpOnMainThread() override {
    TabScrubberChromeOS::GetInstance()->use_default_activation_delay_ = false;
    // Disable external monitor scaling of coordinates.
    ash::Shell* shell = ash::Shell::Get();
    shell->event_transformation_handler()->set_transformation_mode(
        ash::EventTransformationHandler::TRANSFORM_NONE);
  }

  void TearDownOnMainThread() override {
    browser()->tab_strip_model()->RemoveObserver(this);
  }

  TabStrip* GetTabStrip(Browser* browser) {
    aura::Window* window = browser->window()->GetNativeWindow();
    // This test depends on TabStrip impl.
    TabStrip* tab_strip =
        BrowserView::GetBrowserViewForNativeWindow(window)->tabstrip();
    DCHECK(tab_strip);
    return tab_strip;
  }

  int GetStartX(Browser* browser,
                int index,
                TabScrubberChromeOS::Direction direction) {
    return TabScrubberChromeOS::GetStartPoint(GetTabStrip(browser), index,
                                              direction)
        .x();
  }

  int GetTabCenter(Browser* browser, int index) {
    return GetTabStrip(browser)
        ->tab_at(index)
        ->GetMirroredBounds()
        .CenterPoint()
        .x();
  }

  // The simulated scroll event's offsets are calculated in the tests rather
  // than generated by the real event system. For the offsets calculation to be
  // correct in an RTL layout, we must invert the direction since it is
  // calculated here in the tests based on the indices and they're inverted in
  // RTL layouts:
  // Tab indices in an English layout : 0 - 1 - 2 - 3 - 4.
  // Tab indices in an Arabic layout  : 4 - 3 - 2 - 1 - 0.
  TabScrubberChromeOS::Direction InvertDirectionIfNeeded(
      TabScrubberChromeOS::Direction direction) {
    if (base::i18n::IsRTL()) {
      return direction == TabScrubberChromeOS::LEFT ? TabScrubberChromeOS::RIGHT
                                                    : TabScrubberChromeOS::LEFT;
    }

    return direction;
  }

  // Sends one scroll event synchronously without initial or final
  // fling events.
  void SendScrubEvent(Browser* browser, int index) {
    auto event_generator = CreateEventGenerator(browser);
    int active_index = browser->tab_strip_model()->active_index();
    TabScrubberChromeOS::Direction direction = index < active_index
                                                   ? TabScrubberChromeOS::LEFT
                                                   : TabScrubberChromeOS::RIGHT;

    direction = InvertDirectionIfNeeded(direction);

    int offset = GetTabCenter(browser, index) -
                 GetStartX(browser, active_index, direction);
    ui::ScrollEvent scroll_event(ui::ET_SCROLL, gfx::Point(0, 0),
                                 ui::EventTimeForNow(), 0, offset, 0, offset, 0,
                                 kScrubbingGestureFingerCount);
    event_generator->Dispatch(&scroll_event);
  }

  enum ScrubType {
    EACH_TAB,
    SKIP_TABS,
    REPEAT_TABS,
  };

  // Sends asynchronous events and waits for tab at |index| to become
  // active.
  void Scrub(Browser* browser, int index, ScrubType scrub_type) {
    auto event_generator = CreateEventGenerator(browser);
    activation_order_.clear();
    int active_index = browser->tab_strip_model()->active_index();
    ASSERT_NE(index, active_index);
    ASSERT_TRUE(scrub_type != SKIP_TABS || ((index - active_index) % 2) == 0);
    TabScrubberChromeOS::Direction direction;
    int increment;
    if (index < active_index) {
      direction = TabScrubberChromeOS::LEFT;
      increment = -1;
    } else {
      direction = TabScrubberChromeOS::RIGHT;
      increment = 1;
    }

    direction = InvertDirectionIfNeeded(direction);

    if (scrub_type == SKIP_TABS)
      increment *= 2;
    browser->tab_strip_model()->AddObserver(this);
    ScrollGenerator scroll_generator(event_generator.get());
    int last = GetStartX(browser, active_index, direction);
    for (int i = active_index + increment; i != (index + increment);
         i += increment) {
      int tab_center = GetTabCenter(browser, i);
      scroll_generator.GenerateScroll(tab_center - last);
      last = GetStartX(browser, i, direction);
      if (scrub_type == REPEAT_TABS) {
        scroll_generator.GenerateScroll(increment);
        last += increment;
      }
    }
    browser->tab_strip_model()->RemoveObserver(this);
  }

  // Sends events and waits for tab at |index| to become active
  // if it's different from the currently active tab.
  // If the active tab is expected to stay the same, send events
  // synchronously (as we don't have anything to wait for).
  void SendScrubSequence(Browser* browser, float x_offset, int index) {
    auto event_generator = CreateEventGenerator(browser);
    browser->tab_strip_model()->AddObserver(this);
    ScrollGenerator scroll_generator(event_generator.get());
    scroll_generator.GenerateScroll(x_offset);
    browser->tab_strip_model()->RemoveObserver(this);
  }

  // Sends alt-tab key press event to start the window cycle list.
  void StartCyclingWindows(Browser* browser) {
    auto event_generator = CreateEventGenerator(browser);
    // Views use VKEY_MENU for both left and right Alt keys.
    event_generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
    event_generator->PressKey(ui::KeyboardCode::VKEY_TAB, ui::EF_ALT_DOWN);
    event_generator->ReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_ALT_DOWN);
  }

  // Sends alt-tab key release event to start the window cycle list.
  void StopCyclingWindows(Browser* browser) {
    auto event_generator = CreateEventGenerator(browser);
    event_generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  }

  bool IsTabScrubberChromeOSEnabled() {
    return TabScrubberChromeOS::GetInstance()->GetEnabledForTesting();
  }

  void AddTabs(Browser* browser, int num_tabs) {
    TabStrip* tab_strip = GetTabStrip(browser);
    for (int i = 0; i < num_tabs; ++i)
      AddBlankTabAndShow(browser);
    ASSERT_EQ(num_tabs + 1, browser->tab_strip_model()->count());
    ASSERT_EQ(num_tabs, browser->tab_strip_model()->active_index());
    tab_strip->StopAnimating(true);
    ASSERT_FALSE(tab_strip->IsAnimating());
    // Perform any scheduled layouts so the tabstrip is in a steady state.
    BrowserView::GetBrowserViewForBrowser(browser)
        ->GetWidget()
        ->LayoutRootViewIfNecessary();
  }

  // TabStripModelObserver overrides.
  void OnTabStripModelChanged(
      TabStripModel* tab_strip_model,
      const TabStripModelChange& change,
      const TabStripSelectionChange& selection) override {
    if (tab_strip_model->empty() || !selection.active_tab_changed())
      return;

    activation_order_.push_back(selection.new_model.active());
  }

  // History of tab activation. Scrub() resets it.
  std::vector<int> activation_order_;

 private:
  // Used to generate a sequence of scrolls. Starts with a cancel, is followed
  // by any number of scrolls and finally a fling-start. After every event this
  // forces the TabScrubberChromeOS to complete any pending activation.
  class ScrollGenerator {
   public:
    explicit ScrollGenerator(ui::test::EventGenerator* event_generator)
        : event_generator_(event_generator) {
      ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL, gfx::Point(),
                                   time_for_next_event_, 0, 0, 0, 0, 0,
                                   kScrubbingGestureFingerCount);
      event_generator->Dispatch(&fling_cancel);
      if (TabScrubberChromeOS::GetInstance()->IsActivationPending())
        TabScrubberChromeOS::GetInstance()->FinishScrub(true);
    }

    ScrollGenerator(const ScrollGenerator&) = delete;
    ScrollGenerator& operator=(const ScrollGenerator&) = delete;

    ~ScrollGenerator() {
      ui::ScrollEvent fling_start(
          ui::ET_SCROLL_FLING_START, gfx::Point(), time_for_next_event_, 0,
          last_x_offset_, 0, last_x_offset_, 0, kScrubbingGestureFingerCount);
      event_generator_->Dispatch(&fling_start);
      if (TabScrubberChromeOS::GetInstance()->IsActivationPending())
        TabScrubberChromeOS::GetInstance()->FinishScrub(true);
    }

    void GenerateScroll(int x_offset) {
      time_for_next_event_ += base::Milliseconds(100);
      ui::ScrollEvent scroll(ui::ET_SCROLL, gfx::Point(), time_for_next_event_,
                             0, x_offset, 0, x_offset, 0,
                             kScrubbingGestureFingerCount);
      last_x_offset_ = x_offset;
      event_generator_->Dispatch(&scroll);
      if (TabScrubberChromeOS::GetInstance()->IsActivationPending())
        TabScrubberChromeOS::GetInstance()->FinishScrub(true);
    }

   private:
    ui::test::EventGenerator* event_generator_;
    base::TimeTicks time_for_next_event_ = ui::EventTimeForNow();
    int last_x_offset_ = 0;
  };

  std::unique_ptr<ui::test::EventGenerator> CreateEventGenerator(
      Browser* browser) {
    aura::Window* window = browser->window()->GetNativeWindow();
    aura::Window* root = window->GetRootWindow();
    return std::make_unique<ui::test::EventGenerator>(root, window);
  }
};

// Swipe a single tab in each direction.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, Single) {
  AddTabs(browser(), 1);

  Scrub(browser(), 0, EACH_TAB);
  EXPECT_EQ(1U, activation_order_.size());
  EXPECT_EQ(0, activation_order_[0]);
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());

  Scrub(browser(), 1, EACH_TAB);
  EXPECT_EQ(1U, activation_order_.size());
  EXPECT_EQ(1, activation_order_[0]);
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
}

// Swipe 4 tabs in each direction. Each of the tabs should become active.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, Multi) {
  AddTabs(browser(), 4);

  Scrub(browser(), 0, EACH_TAB);
  ASSERT_EQ(4U, activation_order_.size());
  EXPECT_EQ(3, activation_order_[0]);
  EXPECT_EQ(2, activation_order_[1]);
  EXPECT_EQ(1, activation_order_[2]);
  EXPECT_EQ(0, activation_order_[3]);
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());

  Scrub(browser(), 4, EACH_TAB);
  ASSERT_EQ(4U, activation_order_.size());
  EXPECT_EQ(1, activation_order_[0]);
  EXPECT_EQ(2, activation_order_[1]);
  EXPECT_EQ(3, activation_order_[2]);
  EXPECT_EQ(4, activation_order_[3]);
  EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
}

IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, MultiBrowser) {
  AddTabs(browser(), 1);
  Scrub(browser(), 0, EACH_TAB);
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());

  Browser* browser2 = CreateBrowser(browser()->profile());
  browser2->window()->Activate();
  ASSERT_TRUE(browser2->window()->IsActive());
  ASSERT_FALSE(browser()->window()->IsActive());
  AddTabs(browser2, 1);

  Scrub(browser2, 0, EACH_TAB);
  EXPECT_EQ(0, browser2->tab_strip_model()->active_index());
}

// Tests that tab scrubbing works correctly for a full-screen browser.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, FullScreenBrowser) {
  // Initializes the position of mouse. Makes the mouse away from the tabstrip
  // to prevent any interference on this test.
  ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(
      gfx::Point(0, browser()->window()->GetBounds().height())));
  AddTabs(browser(), 6);
  browser()->tab_strip_model()->ActivateTabAt(4);

  chrome::ToggleFullscreenMode(browser());
  BrowserView* browser_view = BrowserView::GetBrowserViewForNativeWindow(
      browser()->window()->GetNativeWindow());
  ImmersiveModeController* immersive_controller =
      browser_view->immersive_mode_controller();
  EXPECT_TRUE(immersive_controller->IsEnabled());

  ImmersiveRevealEndedWaiter waiter(immersive_controller);
  waiter.Wait();

  EXPECT_FALSE(immersive_controller->IsRevealed());

  EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
  Scrub(browser(), 0, EACH_TAB);
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
  EXPECT_EQ(4U, activation_order_.size());
  EXPECT_EQ(3, activation_order_[0]);
  EXPECT_EQ(2, activation_order_[1]);
  EXPECT_EQ(1, activation_order_[2]);
  EXPECT_EQ(0, activation_order_[3]);
}

// Swipe 4 tabs in each direction with an extra swipe within each. The same
// 4 tabs should become active.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, Repeated) {
  AddTabs(browser(), 4);

  Scrub(browser(), 0, REPEAT_TABS);
  ASSERT_EQ(4U, activation_order_.size());
  EXPECT_EQ(3, activation_order_[0]);
  EXPECT_EQ(2, activation_order_[1]);
  EXPECT_EQ(1, activation_order_[2]);
  EXPECT_EQ(0, activation_order_[3]);
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());

  Scrub(browser(), 4, REPEAT_TABS);
  ASSERT_EQ(4U, activation_order_.size());
  EXPECT_EQ(1, activation_order_[0]);
  EXPECT_EQ(2, activation_order_[1]);
  EXPECT_EQ(3, activation_order_[2]);
  EXPECT_EQ(4, activation_order_[3]);
  EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
}

// Confirm that we get the last tab made active when we skip tabs.
// These tests have 5 total tabs. We will only received scroll events
// on tabs 0, 2 and 4.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, Skipped) {
  AddTabs(browser(), 4);

  Scrub(browser(), 0, SKIP_TABS);
  EXPECT_EQ(2U, activation_order_.size());
  EXPECT_EQ(2, activation_order_[0]);
  EXPECT_EQ(0, activation_order_[1]);
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());

  Scrub(browser(), 4, SKIP_TABS);
  EXPECT_EQ(2U, activation_order_.size());
  EXPECT_EQ(2, activation_order_[0]);
  EXPECT_EQ(4, activation_order_[1]);
  EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
}

// Confirm that nothing happens when the swipe is small.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, NoChange) {
  AddTabs(browser(), 1);

  SendScrubSequence(browser(), -1, 1);
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());

  SendScrubSequence(browser(), 1, 1);
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
}

// Confirm that very large swipes go to the beginning and and of the tabstrip.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, Bounds) {
  AddTabs(browser(), 1);

  SendScrubSequence(browser(), -10000, 0);
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());

  SendScrubSequence(browser(), 10000, 1);
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
}

IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, DeleteHighlighted) {
  AddTabs(browser(), 1);

  SendScrubEvent(browser(), 0);
  EXPECT_TRUE(TabScrubberChromeOS::GetInstance()->IsActivationPending());
  browser()->tab_strip_model()->CloseWebContentsAt(0,
                                                   TabStripModel::CLOSE_NONE);
  EXPECT_FALSE(TabScrubberChromeOS::GetInstance()->IsActivationPending());
}

// Delete the currently highlighted tab. Make sure the TabScrubberChromeOS is
// aware.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, DeleteBeforeHighlighted) {
  AddTabs(browser(), 2);

  SendScrubEvent(browser(), 1);
  EXPECT_TRUE(TabScrubberChromeOS::GetInstance()->IsActivationPending());
  browser()->tab_strip_model()->CloseWebContentsAt(0,
                                                   TabStripModel::CLOSE_NONE);
  EXPECT_EQ(0, TabScrubberChromeOS::GetInstance()->highlighted_tab());
}

// Move the currently highlighted tab and confirm it gets tracked.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, MoveHighlighted) {
  AddTabs(browser(), 1);

  SendScrubEvent(browser(), 0);
  EXPECT_TRUE(TabScrubberChromeOS::GetInstance()->IsActivationPending());
  browser()->tab_strip_model()->ToggleSelectionAt(0);
  browser()->tab_strip_model()->ToggleSelectionAt(1);
  browser()->tab_strip_model()->MoveSelectedTabsTo(1);
  EXPECT_EQ(1, TabScrubberChromeOS::GetInstance()->highlighted_tab());
}

// Move a tab to before the highlighted one. Make sure that the highlighted tab
// index is updated correctly.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, MoveBefore) {
  AddTabs(browser(), 2);

  SendScrubEvent(browser(), 1);
  EXPECT_TRUE(TabScrubberChromeOS::GetInstance()->IsActivationPending());
  browser()->tab_strip_model()->ToggleSelectionAt(0);
  browser()->tab_strip_model()->ToggleSelectionAt(2);
  browser()->tab_strip_model()->MoveSelectedTabsTo(2);
  EXPECT_EQ(0, TabScrubberChromeOS::GetInstance()->highlighted_tab());
}

// Move a tab to after the highlighted one. Make sure that the highlighted tab
// index is updated correctly.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, MoveAfter) {
  AddTabs(browser(), 2);

  SendScrubEvent(browser(), 1);
  EXPECT_TRUE(TabScrubberChromeOS::GetInstance()->IsActivationPending());
  browser()->tab_strip_model()->MoveSelectedTabsTo(0);
  EXPECT_EQ(2, TabScrubberChromeOS::GetInstance()->highlighted_tab());
}

// Close the browser while an activation is pending.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, CloseBrowser) {
  AddTabs(browser(), 1);

  SendScrubEvent(browser(), 0);
  EXPECT_TRUE(TabScrubberChromeOS::GetInstance()->IsActivationPending());
  browser()->window()->Close();
  EXPECT_FALSE(TabScrubberChromeOS::GetInstance()->IsActivationPending());
}

// In an RTL layout, swipe 4 tabs in each direction. Each of the tabs should
// become active.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, RTLMulti) {
  base::i18n::SetICUDefaultLocale("ar");
  ASSERT_TRUE(base::i18n::IsRTL());

  AddTabs(browser(), 4);

  Scrub(browser(), 0, EACH_TAB);
  ASSERT_EQ(4U, activation_order_.size());
  EXPECT_EQ(3, activation_order_[0]);
  EXPECT_EQ(2, activation_order_[1]);
  EXPECT_EQ(1, activation_order_[2]);
  EXPECT_EQ(0, activation_order_[3]);
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());

  Scrub(browser(), 4, EACH_TAB);
  ASSERT_EQ(4U, activation_order_.size());
  EXPECT_EQ(1, activation_order_[0]);
  EXPECT_EQ(2, activation_order_[1]);
  EXPECT_EQ(3, activation_order_[2]);
  EXPECT_EQ(4, activation_order_[3]);
  EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
}

// In an RTL layout, confirm that we get the last tab made active when we skip
// tabs. These tests have 5 total tabs. We will only received scroll events
// on tabs 0, 2 and 4.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, RTLSkipped) {
  base::i18n::SetICUDefaultLocale("ar");
  ASSERT_TRUE(base::i18n::IsRTL());

  AddTabs(browser(), 4);

  Scrub(browser(), 0, SKIP_TABS);
  EXPECT_EQ(2U, activation_order_.size());
  EXPECT_EQ(2, activation_order_[0]);
  EXPECT_EQ(0, activation_order_[1]);
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());

  Scrub(browser(), 4, SKIP_TABS);
  EXPECT_EQ(2U, activation_order_.size());
  EXPECT_EQ(2, activation_order_[0]);
  EXPECT_EQ(4, activation_order_[1]);
  EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
}

// In an RTL layout, move a tab to before the highlighted one. Make sure that
// the highlighted tab index is updated correctly.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, RTLMoveBefore) {
  base::i18n::SetICUDefaultLocale("ar");
  ASSERT_TRUE(base::i18n::IsRTL());

  AddTabs(browser(), 2);

  SendScrubEvent(browser(), 1);
  EXPECT_TRUE(TabScrubberChromeOS::GetInstance()->IsActivationPending());
  browser()->tab_strip_model()->ToggleSelectionAt(0);
  browser()->tab_strip_model()->ToggleSelectionAt(2);
  browser()->tab_strip_model()->MoveSelectedTabsTo(2);
  EXPECT_EQ(0, TabScrubberChromeOS::GetInstance()->highlighted_tab());
}

// If the window cycle list is open, the tab scrubber should be disabled.
IN_PROC_BROWSER_TEST_F(TabScrubberChromeOSTest, DisabledIfWindowCycleListOpen) {
  AddTabs(browser(), 4);

  // Create a second browser, but don't make it active.
  Browser* browser2 = CreateBrowser(browser()->profile());
  browser()->window()->Activate();
  ASSERT_FALSE(browser2->window()->IsActive());
  ASSERT_TRUE(browser()->window()->IsActive());

  // Open window cycle list. It should be open now so tab scrubber should be
  // disabled.
  StartCyclingWindows(browser());
  EXPECT_FALSE(IsTabScrubberChromeOSEnabled());
  Scrub(browser(), 0, EACH_TAB);
  EXPECT_EQ(0u, activation_order_.size());
  EXPECT_EQ(4, browser()->tab_strip_model()->active_index());

  // Stop cycling. Scrub should work again.
  StopCyclingWindows(browser());
  EXPECT_TRUE(IsTabScrubberChromeOSEnabled());
  Scrub(browser(), 0, EACH_TAB);
  ASSERT_EQ(4U, activation_order_.size());
  EXPECT_EQ(3, activation_order_[0]);
  EXPECT_EQ(2, activation_order_[1]);
  EXPECT_EQ(1, activation_order_[2]);
  EXPECT_EQ(0, activation_order_[3]);
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
}
